package my.mall.front.api.service

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper
import com.baomidou.mybatisplus.core.metadata.IPage
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import jakarta.annotation.PostConstruct
import jakarta.servlet.http.HttpServletRequest
import my.mall.common.bean.JsonParser
import my.mall.common.enums.RespMetaEnum
import my.mall.common.exception.WebBaseException
import my.mall.common.pojo.CommonResp
import my.mall.common.pojo.PageParam
import my.mall.common.util.BigDecimalUtils.fenToYuan
import my.mall.common.util.DateUtils.formatLocalDateTime
import my.mall.common.util.RespUtils.success
import my.mall.db.entity.*
import my.mall.db.mapper.*
import my.mall.front.api.pojo.order.OrderDetail
import my.mall.front.api.pojo.order.OrderItem
import my.mall.front.api.pojo.order.PayOrderParam
import my.mall.front.api.pojo.order.SubmitOrderParam
import my.mall.front.api.service.pay.PaymentService
import my.mall.front.api.util.MoneyUtils
import my.mall.front.api.util.OrderUtils
import my.mall.front.api.util.SpecUtils
import my.mall.front.api.util.UserUtils
import org.springframework.beans.BeanUtils
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Service
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.annotation.Transactional
import org.springframework.util.CollectionUtils
import org.springframework.util.StringUtils

@Service
open class OrderService(
    private val userMapper: UserMapper,
    private val rewardPointRecordMapper: RewardPointRecordMapper,
    private val goodsMapper: GoodsMapper,
    private val goodsSkuMapper: GoodsSkuMapper,
    private val orderMapper: OrderMapper,
    private val orderAddressMapper: OrderAddressMapper,
    private val orderGoodsMapper: OrderGoodsMapper,
    private val jsonParser: JsonParser,
    private val applicationContext: ApplicationContext,
    private val transactionManager: PlatformTransactionManager,
    private val transactionDefinition: TransactionDefinition
) {

    private lateinit var serviceMap: Map<String, PaymentService>

    @PostConstruct
    fun init() {
        serviceMap = applicationContext.getBeansOfType(PaymentService::class.java)
    }

    private fun paymentService(paymentType: String?): PaymentService? {
        return serviceMap[paymentType]
    }

    private fun paymentService(request: HttpServletRequest): PaymentService? {
        for (paymentService in this.serviceMap.values) {
            if (paymentService.checkType(request)) {
                return paymentService
            }
        }
        return null
    }

    open fun submitOrder(userId: Long, orderParam: SubmitOrderParam): Map<String, Any?> {
        val order = saveOrder(userId, orderParam)
        return getPaymentInfo(order)
    }

    private fun saveOrder(userId: Long, orderParam: SubmitOrderParam): Order {
        var transaction: TransactionStatus? = null
        return try {
            transaction = transactionManager.getTransaction(transactionDefinition)
            val user = userMapper.selectOne(
                QueryWrapper<User>()
                    .select(
                        User.ID,
                        User.USER_STATUS,
                        User.REWARD_POINT,
                    )
                    .eq(User.ID, userId)
            )
            UserUtils.checkUserStatus(user)
            val goodsInfo = orderParam.goodsInfo
            val goods = goodsMapper.selectOne(
                QueryWrapper<Goods>()
                    .select(
                        Goods.ID,
                        Goods.STORE_ID,
                        Goods.GOODS_NAME,
                        Goods.SELLING_POINT,
                        Goods.MAIN_IMG_URL,
                        Goods.GOODS_PRICE,
                        Goods.INIT_SALES_VOLUME,
                        Goods.REAL_SALES_VOLUME,
                        Goods.CATEGORY_ID1,
                        Goods.CATEGORY_ID2,
                        Goods.CATEGORY_ID3,
                    )
                    .eq(Goods.ID, goodsInfo.goodsId)
            ) ?: throw WebBaseException(RespMetaEnum.PARAM_ERROR)
            val sku = goodsSkuMapper.selectOne(
                QueryWrapper<GoodsSku>()
                    .select(
                        GoodsSku.ID,
                        GoodsSku.SPEC_IDS,
                        GoodsSku.SPEC_DETAIL,
                        GoodsSku.STOCK_NUM,
                        GoodsSku.SKU_PRICE,
                        GoodsSku.SKU_IMG_URL
                    )
                    .eq(GoodsSku.ID, goodsInfo.skuId)
            ) ?: throw WebBaseException(RespMetaEnum.PARAM_ERROR)
            if (sku.stockNum!! < goodsInfo.num) {
                throw WebBaseException(560, "库存不足")
            }
            val tolCost = goodsInfo.num * sku.skuPrice!!
            val dataModifyNum: Int = try {
                goodsSkuMapper.subStock(goodsInfo.num, goodsInfo.skuId)
            } catch (e: Throwable) {
                throw WebBaseException(560, "库存不足", e)
            }
            if (dataModifyNum <= 0) {
                throw WebBaseException(560, "库存不足")
            }
            val order = Order()
            order.userId = userId
            order.actualPrice = tolCost
            order.paymentType = orderParam.paymentType
            if (orderMapper.insert(order) <= 0) {
                throw WebBaseException(560, "下单失败!")
            }
            val addressInfo = orderParam.addressInfo
            val address = OrderAddress()
            address.orderId = order.id
            address.username = addressInfo.username
            address.phoneNum = addressInfo.phoneNum
            address.userProvince = addressInfo.userProvince
            address.userCity = addressInfo.userCity
            address.userArea = addressInfo.userArea
            address.userAddress = addressInfo.userAddress
            address.addressType = 0
            if (orderAddressMapper.insert(address) <= 0) {
                throw WebBaseException(560, "下单失败!")
            }
            val orderGoods = OrderGoods()
            orderGoods.orderId = order.id
            orderGoods.goodsId = goodsInfo.goodsId
            orderGoods.skuId = goodsInfo.skuId
            orderGoods.goodsName = goods.goodsName
            orderGoods.buyNum = goodsInfo.num
            orderGoods.skuImgUrl = if (StringUtils.hasLength(sku.skuImgUrl)) sku.skuImgUrl else goods.mainImgUrl
            orderGoods.skuPrice = sku.skuPrice
            orderGoods.specDetail = sku.specDetail
            if (orderGoodsMapper.insert(orderGoods) <= 0) {
                throw WebBaseException(560, "下单失败!")
            }
            transactionManager.commit(transaction!!)
            order
        } catch (e: Throwable) {
            if (transaction != null) {
                transactionManager.rollback(transaction)
            }
            throw e
        }
    }

    open fun getPaymentInfo(userId: Long, param: PayOrderParam): Map<String, Any?> {
        if (!StringUtils.hasLength(param.paymentType)) {
            return emptyMap<String, Any>()
        }
        val order = orderMapper.selectOne(
            QueryWrapper<Order>()
                .select(
                    Order.ID,
                    Order.ORDER_STATUS,
                    Order.ACTUAL_PRICE,
                    Order.CREATED_TIME,
                )
                .eq(Order.ID, param.orderId)
        )
        if (!OrderUtils.canPayment(userId, order)) {
            throw WebBaseException(400, "订单支付中，请稍后")
        }
        orderMapper.update(
            UpdateWrapper<Order>()
                .set(Order.PAYMENT_TYPE, param.paymentType)
                .eq(Order.ID, order.id)
        )
        order.paymentType = param.paymentType
        return getPaymentInfo(order)
    }

    private fun getPaymentInfo(order: Order): Map<String, Any?> {
        val res: MutableMap<String, Any?> = HashMap()
        res["orderId"] = order.id
        val paymentService = paymentService(order.paymentType)
        if (paymentService != null) {
            val content = paymentService.getPaymentPage(order)
            res["content"] = content
        }
        return res
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun payOrder(userId: Long, param: PayOrderParam) {
        val orderId = param.orderId
        val user = userMapper.selectOne(
            QueryWrapper<User>()
                .select(
                    User.ID,
                    User.USER_STATUS,
                    User.REWARD_POINT,
                    User.USER_PASSWORD,
                )
                .eq(User.ID, userId)
        )
        UserUtils.checkUserStatus(user)
        UserUtils.checkUserPassword(param.password, user.userPassword)
        val order = orderMapper.lockById(userId, orderId)
        if (!OrderUtils.canPayment(userId, order)) {
            throw WebBaseException(400, "订单支付中，请稍后")
        }
        val oldStatus = order!!.orderStatus!!
        val newStatus = OrderUtils.changeToWaitDelivery(order)
        var dataModifyNum: Int
        try {
            dataModifyNum = userMapper.payByIntegral(userId, order.actualPrice!!)
            if (dataModifyNum <= 0) {
                throw WebBaseException(400, "余额不足")
            }
        } catch (e: Throwable) {
            throw WebBaseException(400, "余额不足", e)
        }
        val record = RewardPointRecord()
        record.userId = userId
        record.recordType = 1
        record.recordAmount = order.actualPrice
        record.remark = "订单[$orderId]付款"
        if (rewardPointRecordMapper.insert(record) <= 0) {
            throw WebBaseException(400, "订单支付失败")
        }
        dataModifyNum = orderMapper.updateStatus(orderId, newStatus, oldStatus)
        if (dataModifyNum <= 0) {
            throw WebBaseException(400, "订单支付失败")
        }
    }

    open fun paymentCallback(request: HttpServletRequest) {
        val paymentService = paymentService(request)
        paymentService?.callback(request)
    }

    open fun getOrderList(userId: Long?, orderStatus: Int?, pageParam: PageParam): CommonResp<*> {
        var page: IPage<Order> = Page(pageParam.pageNum, pageParam.pageSize)
        page = orderMapper.selectPage(
            page, QueryWrapper<Order>()
                .select(
                    Order.ID,
                    Order.ORDER_STATUS,
                    Order.ACTUAL_PRICE,
                    Order.CREATED_TIME
                )
                .eq(Order.USER_ID, userId)
                .eq(orderStatus != null, Order.ORDER_STATUS, orderStatus)
                .orderByDesc(Order.ID)
        )
        val orderList = page.records
        if (CollectionUtils.isEmpty(orderList)) {
            return success()
        }
        val orderMap: MutableMap<Long, Order> = HashMap(orderList.size)
        for (order in orderList) {
            updateOrderStatus(order)
            orderMap[order.id!!] = order
        }
        val goodsList = orderGoodsMapper.selectList(
            QueryWrapper<OrderGoods>()
                .select(
                    OrderGoods.ORDER_ID,
                    OrderGoods.GOODS_ID,
                    OrderGoods.SKU_ID,
                    OrderGoods.GOODS_NAME,
                    OrderGoods.BUY_NUM,
                    OrderGoods.SKU_IMG_URL,
                    OrderGoods.SKU_PRICE,
                    OrderGoods.SPEC_DETAIL,
                )
                .`in`(OrderGoods.ORDER_ID, orderMap.keys)
        )
        val resList: MutableList<OrderItem> = ArrayList(orderList.size)
        for (goods in goodsList) {
            val order = orderMap[goods.orderId]
            val item = OrderItem()
            BeanUtils.copyProperties(order!!, item)
            BeanUtils.copyProperties(goods, item)
            item.skuPrice = MoneyUtils.convertFenToYuan(goods.skuPrice!!)
            item.actualPrice = MoneyUtils.convertFenToYuan(order!!.actualPrice!!)
            item.orderStatusStr = OrderUtils.statusToStr(order)
            item.specDetail = SpecUtils.parseSpec(jsonParser, goods.specDetail)
            resList.add(item)
        }
        return success(page.current, page.getPages(), page.total, resList)
    }

    open fun getOrderDetail(userId: Long, orderId: Long?): OrderDetail {
        val order = orderMapper.selectOne(
            QueryWrapper<Order>()
                .select(
                    Order.ID,
                    Order.ORDER_STATUS,
                    Order.ACTUAL_PRICE,
                    Order.CREATED_TIME,
                )
                .eq(Order.ID, orderId)
        )
        if (order == null || userId != order.userId) {
            throw WebBaseException(RespMetaEnum.PARAM_ERROR)
        }
        updateOrderStatus(order)
        val goods = orderGoodsMapper.selectOne(
            QueryWrapper<OrderGoods>()
                .select(
                    OrderGoods.ORDER_ID,
                    OrderGoods.GOODS_ID,
                    OrderGoods.SKU_ID,
                    OrderGoods.GOODS_NAME,
                    OrderGoods.BUY_NUM,
                    OrderGoods.SKU_IMG_URL,
                    OrderGoods.SKU_PRICE,
                    OrderGoods.SPEC_DETAIL,
                )
                .eq(OrderGoods.ORDER_ID, orderId)
        )
        val address = orderAddressMapper.selectOne(
            QueryWrapper<OrderAddress>()
                .select(
                    OrderAddress.USERNAME,
                    OrderAddress.PHONE_NUM,
                    OrderAddress.USER_PROVINCE,
                    OrderAddress.USER_CITY,
                    OrderAddress.USER_AREA,
                    OrderAddress.USER_ADDRESS,
                )
                .eq(OrderAddress.ORDER_ID, orderId)
        )
        val orderDetail = OrderDetail()
        BeanUtils.copyProperties(order, orderDetail)
        BeanUtils.copyProperties(goods, orderDetail)
        BeanUtils.copyProperties(address, orderDetail)
        orderDetail.createdTime = formatLocalDateTime(order.createdTime)
        orderDetail.actualPrice = fenToYuan(order.actualPrice)
        orderDetail.skuPrice = fenToYuan(goods.skuPrice)
        orderDetail.specDetail = SpecUtils.parseSpec(jsonParser, goods.specDetail)
        return orderDetail
    }

    private fun updateOrderStatus(order: Order) {
        val paymentService = paymentService(order.paymentType)
        paymentService?.updateOrderStatus(order)
    }
}
